/*
 * Copyright (c) 2017-2019, APT Group, School of Computer Science,
 * The University of Manchester. All rights reserved.
 * Copyright (c) 2014, 2015, Andrey Rodchenko. All rights reserved.
 * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package com.oracle.max.vm.ext.t1x;

import static com.oracle.max.vm.ext.t1x.T1X.*;
import static com.sun.cri.bytecode.Bytecodes.*;
import static com.sun.max.platform.Platform.*;
import static com.sun.max.vm.MaxineVM.*;
import static com.sun.max.vm.compiler.CallEntryPoint.*;
import static com.sun.max.vm.compiler.target.Safepoints.*;
import static com.sun.max.vm.compiler.target.Stub.Type.*;
import static com.sun.max.vm.stack.JVMSFrameLayout.*;
import static com.sun.max.vm.stack.StackReferenceMapPreparer.*;

import java.util.*;

import com.oracle.max.asm.target.riscv64.RISCV64;
import com.oracle.max.criutils.TTY;
import com.oracle.max.vm.ext.t1x.T1XTemplate.*;
import com.sun.cri.bytecode.*;
import com.sun.cri.ci.*;
import com.sun.cri.ci.CiTargetMethod.*;
import com.sun.cri.ri.*;
import com.sun.max.annotate.*;
import com.sun.max.atomic.*;
import com.sun.max.lang.*;
import com.sun.max.platform.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.bytecode.refmaps.*;
import com.sun.max.vm.classfile.*;
import com.sun.max.vm.classfile.constant.*;
import com.sun.max.vm.code.*;
import com.sun.max.vm.code.CodeManager.*;
import com.sun.max.vm.collect.*;
import com.sun.max.vm.compiler.*;
import com.sun.max.vm.compiler.deopt.Deoptimization.*;
import com.sun.max.vm.compiler.target.*;
import com.sun.max.vm.compiler.target.aarch64.*;
import com.sun.max.vm.compiler.target.amd64.*;
import com.sun.max.vm.compiler.target.arm.*;
import com.sun.max.vm.compiler.target.riscv64.RISCV64TargetMethodUtil;
import com.sun.max.vm.object.*;
import com.sun.max.vm.profile.*;
import com.sun.max.vm.reference.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.stack.*;
import com.sun.max.vm.stack.amd64.*;
import com.sun.max.vm.stack.armv7.*;
import com.sun.max.vm.thread.*;
import com.sun.max.vm.type.*;

/**
 * A target method generated by T1X.
 */
public class T1XTargetMethod extends TargetMethod {

    static final int SYNC_METHOD_CATCH_TYPE_CPI = -1;
    public static final String PROTECTED = "PROTECTED";
    static final boolean PRINT_WARNING_ON_NOT_PROFILED_EXCEPTION = false;

    /**
     * This is the max number of slots used by any template and is computed when the templates are
     * {@linkplain T1X#createTemplates(RuntimeCompiler, Class, boolean, T1XTemplate[])}  created}.
     */
    static int templateSlots;

    /**
     * Gets the number of slots to be reserved in each T1X frame for template spill slots.
     */
    public static int templateSlots() {
        return templateSlots;
    }

    /**
     * The frame and register reference maps for this target method.
     * <p/>
     * The format of this byte array is described by the following pseudo C declaration:
     * <p/>
     * <p/>
     * <pre>
     * referenceMaps {
     *     {
     *         u1 frameMap[frameRefMapSize];
     *         u1 regMap[regRefMapSize];
     *     } directCallMaps[numberOfDirectCalls]
     *     {
     *         u1 frameMap[frameRefMapSize];
     *         u1 regMap[regRefMapSize];
     *     } indirectCallMaps[numberOfIndirectCalls]
     *     {
     *         u1 frameMap[frameRefMapSize];
     *         u1 regMap[regRefMapSize];
     *     } safepointMaps[numberOfSafepoints]
     * }
     * </pre>
     */
    private final byte[] refMaps;

    /**
     * Offset from the frame pointer (e.g. %rbp on AMD64) to the bottom of the frame.
     * This is the address to which the frame reference maps are relative.
     */
    public final int frameRefMapOffset;

    public final int frameRefMapSize;

    /**
     * This field represents 1 of 3 ref map finalization states after the constructor of {@link T1XTargetMethod}
     * has completed. These values and the states they represent are:
     * <ol>
     * <li>A {@link T1XReferenceMapEditor} instance. This represents a target method whose ref maps have not yet been finalized.</li>
     * <li>A {@link VmThread} instance. This represents a target method whose ref maps are being finalized (by the denoted thread).</li>
     * <li>A {@code null} value. This represents a target method whose ref maps are finalized.</li>
     * </ol>
     * <p/>
     * Only the transition from state 1 to state 2 is atomic.
     *
     * @see #finalizeReferenceMaps()
     */
    @INSPECTED(deepCopied = false)
    private final AtomicReference refMapEditor = new AtomicReference();

    /**
     * The frame layout.
     */
    public final JVMSFrameLayout frame;

    /**
     * The bytecode from which this target method was compiled. This will differ from
     * the bytecode hanging off of {@link #classMethodActor} in the case where it was rewritten.
     */
    public final CodeAttribute codeAttribute;

    /**
     * An array that encodes a mapping from BCIs to target code positions. A non-zero value
     * {@code val} at index {@code i} in the array encodes that there is a bytecode instruction whose opcode is at index
     * {@code i} in the bytecode array and whose target code position is {@code val}. Unless {@code i} is equal to the
     * length of the bytecode array in which case {@code val} denotes the target code position one byte past the
     * last target code byte emitted for the last bytecode instruction.
     */
    public final int[] bciToPos;

    public final CiExceptionHandler[] handlers;

    /**
     * The code annotations (if any) recorded when compiling this T1X method.
     */
    public final CodeAnnotation[] annotations;

    /**
     * The profile for this method - if there is one (otherwise, null).
     */
    public final MethodProfile profile;

    /**
     * Index for the method protection sentinel (for code eviction) in the reference literals array.
     */
    public final int protectionLiteralIndex;

    /**
     * Number of {@linkplain CodeEviction code eviction cycles} this method survived.
     */
    private int survivedEvictions = 0;

    public T1XTargetMethod(T1XCompilation comp, boolean install) {
        super(comp.method, CallEntryPoint.BASELINE_ENTRY_POINT);
        codeAttribute = comp.codeAttribute;
        bciToPos = comp.bciToPos;
        frame = comp.frame;
        frameRefMapOffset = frame.frameReferenceMapOffset();
        setFrameSize(frame.frameSize());
        frameRefMapSize = frame.frameReferenceMapSize();
        if (comp.codeAnnotations == null || comp.codeAnnotations.isEmpty()) {
            annotations = null;
        } else {
            annotations = comp.codeAnnotations.toArray(new CodeAnnotation[comp.codeAnnotations.size()]);
        }

        SafepointsBuilder safepointsBuilder = comp.safepointsBuilder;
        int firstTemplateSlot = frame.numberOfNonParameterSlots() + frame.numberOfOperandStackSlots();
        int firstTemplateSlotIndexInFrameReferenceMap = firstTemplateSlot * JVMSFrameLayout.STACK_SLOTS_PER_JVMS_SLOT;
        safepointsBuilder.pack(frameRefMapSize, regRefMapSize(), firstTemplateSlotIndexInFrameReferenceMap, comp.adapter);
        setSafepoints(safepointsBuilder.safepoints, safepointsBuilder.directCallees);
        refMaps = safepointsBuilder.refMaps;
        handlers = initHandlers(comp);

        if (comp.methodProfileBuilder != null) {
            comp.methodProfileBuilder.finish(this);
            profile = comp.methodProfileBuilder.methodProfileObject();
        } else {
            profile = null;
        }

        assert comp.protectionLiteralIndex == 0 : "protection literal should be first but is " + comp.protectionLiteralIndex;
        protectionLiteralIndex = comp.protectionLiteralIndex;

        final TargetBundleLayout targetBundleLayout;

        targetBundleLayout = new TargetBundleLayout(0, comp.objectLiterals.size(), comp.buf.position(), comp.trampolines == null ? 0 : comp.trampolines.length);

        // Allocate and set the code and data buffer
        if (install) {
            Code.allocate(targetBundleLayout, this);
        } else {
            Code.allocateInHeap(targetBundleLayout, this);
        }

        // Copy code
        comp.buf.copyInto(code(), 0, code().length);

        // Check whether the target method uses trampolines. For instance a
        // method might not have any callees and hence trampolines set.
        if (trampolines() != null && trampolines().length != 0) {
            System.arraycopy(comp.trampolines, 0, trampolines(), 0, trampolinesLength());
        }

        // Copy reference literals
        if (referenceLiterals != null) {
            // Must not cause checkcast here, since some reference literals may be static tuples.
            for (int i = 0; i < comp.objectLiterals.size(); i++) {
                Object literal = comp.objectLiterals.get(i);
                ArrayAccess.setObject(referenceLiterals, i, literal);
            }
        }

        assert referenceLiterals != null;
        assert referenceLiterals[protectionLiteralIndex] == PROTECTED : "expected protection literal, found " + referenceLiterals[protectionLiteralIndex];

        if (safepointsBuilder.safepoints.size() != 0) {
            final T1XReferenceMapEditor referenceMapEditor = new T1XReferenceMapEditor(this, comp.numberOfBlocks, comp.blockBCIs, safepointsBuilder.bytecodeSafepointsIterator, frame);
            this.refMapEditor.set(referenceMapEditor);
            final ReferenceMapInterpreter interpreter = ReferenceMapInterpreter.from(referenceMapEditor.blockFrames());
            if (interpreter.performsAllocation() || T1XOptions.EagerRefMaps || T1XOptions.PrintCFGToFile) {
                finalizeReferenceMaps();
            }
        }

        if (!MaxineVM.isHosted() && install) {
            linkDirectCalls();
            // Perform cache maintenance after linking calls to ensure visibility of fixed call-sites.
            maybeCleanCache();
        }

        // if the VM is running, validate freshly generated code
        assert MaxineVM.isHosted() || CodeCacheValidation.instance.validateSingleMethod(this);
    }

    @Override
    public VMFrameLayout frameLayout() {
        return frame;
    }

    @Override
    public Lifespan lifespan() {
        return (classMethodActor != null && classMethodActor.isClassInitializer()) ? Lifespan.ONE_SHOT : Lifespan.SHORT;
    }

    @Override
    public boolean isProtected() {
        return referenceLiterals[protectionLiteralIndex] == PROTECTED;
    }

    @Override
    public void protect() {
        referenceLiterals[protectionLiteralIndex] = PROTECTED;
    }

    /**
     * Notify this method of a survived {@linkplain CodeEviction code eviction cycle}. This is done
     * by simply incrementing the {@link #survivedEvictions} counter.
     */
    @Override
    public void survivedEviction() {
        ++survivedEvictions;
    }

    /**
     * This method was {@linkplain CodeEviction evicted} if its {@link #survivedEvictions} counter
     * is smaller than the current eviction count.
     */
    @Override
    public boolean wasEvicted() {
        return survivedEvictions < CodeEviction.evictionCount();
    }

    /**
     * The number of times this method was relocated is equal to the number of {@linkplain CodeEviction
     * eviction cycles} it survived.
     */
    @Override
    public int timesRelocated() {
        return survivedEvictions;
    }

    @Override
    public MethodProfile profile() {
        return profile;
    }

    @Override
    public int[] bciToPosMap() {
        return bciToPos;
    }

    @Override
    public CodeAttribute codeAttribute() {
        return codeAttribute;
    }

    /**
     * Initializes the exception handlers for this method, adding an extra one if necessary for
     * a synchronized method. This synthesized handler covers the machine code from the
     * instruction immediately after the lock on the receiver/class was acquired to the
     * instruction just after the lock is released. This mimics the exception handler
     * range generated by Java source compilers for synchronized blocks. The synthesized
     * handler is in terms of machine code positions, not BCIs and is distinguished by
     * its {@link CiExceptionHandler#catchTypeCPI} being {@link #SYNC_METHOD_CATCH_TYPE_CPI}.
     */
    private CiExceptionHandler[] initHandlers(T1XCompilation comp) {
        if (comp.method.isSynchronized()) {
            CiExceptionHandler[] handlers = comp.handlers;
            CiExceptionHandler[] newHandlers = Arrays.copyOf(handlers, handlers.length + 1);
            CiExceptionHandler syncMethodHandler =
                new CiExceptionHandler(comp.syncHandlerStartPos,
                                       comp.syncHandlerEndPos,
                                       comp.syncMethodHandlerPos,
                                       SYNC_METHOD_CATCH_TYPE_CPI,
                                       null);
            newHandlers[handlers.length] = syncMethodHandler;

            // Update the reference maps to cover the local variable holding the copy of the receiver
            if (comp.synchronizedReceiver != -1) {
                for (int safepointIndex = 0; safepointIndex < safepoints.size(); safepointIndex++) {
                    int pos = safepoints.posAt(safepointIndex);
                    if (pos >= comp.syncRefMapStartPos && pos < comp.syncRefMapEndPos) {
                        final int offset = safepointIndex * refMapSize();
                        final int refMapBit = frame.localVariableReferenceMapIndex(comp.synchronizedReceiver);
                        ByteArrayBitMap.set(refMaps, offset, frameRefMapSize, refMapBit);
                    }
                }
            }
            return newHandlers;
        }
        return comp.handlers;
    }

    int sizeOfNonParameterLocals() {
        return JVMSFrameLayout.JVMS_SLOT_SIZE * frame.numberOfNonParameterSlots();
    }

    @HOSTED_ONLY
    @Override
    public void gatherCalls(Set<MethodActor> directCalls, Set<MethodActor> virtualCalls, Set<MethodActor> interfaceCalls, Set<MethodActor> inlinedMethods) {
        for (int i = 0; i < safepoints.size(); ++i) {
            int bci = bciForPos(safepoints.posAt(i));
            if (bci != -1) {
                RiMethod callee = codeAttribute.calleeAt(bci);
                if (callee instanceof MethodActor) {
                    MethodActor ma = (MethodActor) callee;
                    int opcode = codeAttribute.code()[bci] & 0xff;
                    if (opcode == INVOKEVIRTUAL) {
                        virtualCalls.add(ma);
                    } else if (opcode == INVOKESTATIC || opcode == INVOKESPECIAL) {
                        directCalls.add(ma);
                    } else {
                        assert opcode == INVOKEINTERFACE;
                        interfaceCalls.add(ma);
                    }
                }
            }
        }
    }

    @Override
    public boolean isPatchableCallSite(CodePointer callSite) {
        if (isAMD64()) {
            return AMD64TargetMethodUtil.isPatchableCallSite(callSite);
        } else if (isARM()) {
            return ARMTargetMethodUtil.isPatchableCallSite(callSite);
        } else if (isAARCH64()) {
            return Aarch64TargetMethodUtil.isPatchableCallSite(callSite);
        } else if (isRISCV64()) {
            return RISCV64TargetMethodUtil.isPatchableCallSite(callSite);
        } else {
            throw FatalError.unimplemented("com.oracle.max.vm.ext.t1x.T1XTargetMethod.isPatchableCallSite");
        }
    }

    @Override
    public CodePointer fixupCallSite(int callOffset, CodePointer callEntryPoint) {
        if (isAMD64()) {
            return AMD64TargetMethodUtil.fixupCall32Site(this, callOffset, callEntryPoint);
        } else if (isARM()) {
            return ARMTargetMethodUtil.fixupCall32Site(this, callOffset, callEntryPoint);
        } else if (isAARCH64()) {
            return Aarch64TargetMethodUtil.fixupCall32Site(this, callOffset, callEntryPoint);
        } else if (isRISCV64()) {
            return RISCV64TargetMethodUtil.fixupCall32Site(this, callOffset, callEntryPoint);
        } else {
            throw FatalError.unimplemented("com.oracle.max.vm.ext.t1x.T1XTargetMethod.fixupCallSite");
        }
    }

    public byte[] referenceMaps() {
        return refMaps;
    }

    /**
     * Gets the size (in bytes) of a bit map covering all the registers that may store references.
     * The bit position of a register in the bit map is the register's {@linkplain CiRegister#encoding encoding}.
     */
    @FOLD
    static int regRefMapSize() {
        return ByteArrayBitMap.computeBitMapSize(target().arch.registerReferenceMapBitCount);
    }

    /**
     * This version of {@code posFor()} accounts for the possibility of a method having moved in the code cache.
     */
    @Override
    public int posFor(CodePointer ip) {
        int pos = super.posFor(ip);
        if (pos == -1) {
            if (Code.getCodeManager().getRuntimeBaselineCodeRegion().isInFromSpace(ip.toAddress())) {
                pos = (int) (ip.toLong() - (oldStart.toLong() + codeStart().toLong() - start().toLong()));
            }
            if (pos < 0 && pos > code().length) {
                pos = -1;
            }
        }
        return pos;
    }

    @Override
    public CodePointer patchCallSite(int callOffset, CodePointer callEntryPoint) {
        if (isAMD64()) {
            return AMD64TargetMethodUtil.mtSafePatchCallDisplacement(this, codeAt(callOffset), callEntryPoint);
        } else if (isARM()) {
            return ARMTargetMethodUtil.mtSafePatchCallDisplacement(this, codeAt(callOffset), callEntryPoint);
        } else if (isAARCH64()) {
            return Aarch64TargetMethodUtil.mtSafePatchCallDisplacement(this, codeAt(callOffset), callEntryPoint);
        } else if (isRISCV64()) {
            return RISCV64TargetMethodUtil.mtSafePatchCallDisplacement(this, codeAt(callOffset), callEntryPoint);
        } else {
            throw FatalError.unimplemented("com.oracle.max.vm.ext.t1x.T1XTargetMethod.patchCallSite");
        }
    }

    @Override
    public void redirectTo(TargetMethod tm) {
        if (platform().isa == ISA.AMD64) {
            AMD64TargetMethodUtil.patchWithJump(this, BASELINE_ENTRY_POINT.offset(), BASELINE_ENTRY_POINT.in(tm));
            if (vm().compilationBroker.needsAdapters()) {
                AMD64TargetMethodUtil.patchWithJump(this, OPTIMIZED_ENTRY_POINT.offset(), OPTIMIZED_ENTRY_POINT.in(tm));
            }
        } else if (platform().isa == ISA.ARM) {
            ARMTargetMethodUtil.patchWithJump(this, BASELINE_ENTRY_POINT.offset(), BASELINE_ENTRY_POINT.in(tm));
            if (vm().compilationBroker.needsAdapters()) {
                ARMTargetMethodUtil.patchWithJump(this, OPTIMIZED_ENTRY_POINT.offset(), OPTIMIZED_ENTRY_POINT.in(tm));
            }
        } else {
            unimplISA();
        }
    }

    @Override
    protected CallEntryPoint callEntryPointForDirectCall(int safepointIndex) {
        if (!safepoints.isSetAt(TEMPLATE_CALL, safepointIndex)) {
            return CallEntryPoint.OPTIMIZED_ENTRY_POINT;
        }
        return CallEntryPoint.BASELINE_ENTRY_POINT;
    }

    @INLINE
    private CodePointer relocateIP(CodePointer ip) {
        return CodePointer.from(start).plus(ip.minus(oldStart));
    }

    public int posForBci(int bci) {
        return bciToPos[bci];
    }

    @Override
    public CodeAnnotation[] annotations() {
        return annotations;
    }

    @Override
    public CiDebugInfo debugInfoAt(int safepointIndex, FrameAccess fa) {
        CiBitMap frameRefMap = new CiBitMap(referenceMaps(), safepointIndex * refMapSize(), frameRefMapSize);
        CiBitMap regRefMap = new CiBitMap(referenceMaps(), (safepointIndex * refMapSize()) + frameRefMapSize, regRefMapSize());
        int bci = bciForPos(safepoints.posAt(safepointIndex));
        CiFrame debugFrame = frame.asFrame(classMethodActor, bci, frameRefMap);
        if (fa != null) {
            CiValue[] actualValues = new CiValue[debugFrame.values.length];
            // somewhat wasteful as we just replace all the recipe values with the actual values
            for (int i = 0; i < debugFrame.values.length; i++) {
                CiValue accessValue = debugFrame.values[i];
                actualValues[i] = toLiveSlot(fa, accessValue);
            }
            debugFrame = new CiFrame(debugFrame.caller(), debugFrame.method, debugFrame.bci,
                                     debugFrame.rethrowException, actualValues,
                                     debugFrame.numLocals, debugFrame.numStack,
                                     debugFrame.numLocks);
        }
        return new CiDebugInfo(debugFrame, regRefMap, frameRefMap);
    }

    private static CiValue toLiveSlot(FrameAccess fa, CiValue value) {
        CiAddress address = (CiAddress) value;
        Pointer fp = fa.fp;
        if (value.kind.isObject()) {
            Reference ref = fp.readReference(address.displacement);
            value = CiConstant.forObject(ref.toJava());
        } else {
            Word w = fp.readWord(address.displacement);
            value = WordUtil.archConstant(w);
        }
        return value;
    }

    @Override
    public int forEachCodePos(CodePosClosure cpc, CodePointer ip) {
        int bci = bciFor(ip);
        if (bci >= 0) {
            cpc.doCodePos(classMethodActor, bci);
            return 1;
        }
        return 0;
    }

    /**
     * Gets the BCI for a machine code instruction address.
     *
     * @param ip an instruction pointer that may denote an instruction in this target method
     * @return the start position of the bytecode instruction that is implemented at the instruction pointer or -1 if
     *         {@code instructionPointer} denotes an instruction that does not correlate to any bytecode.  This will be
     *         the case when {@code instructionPointer} is not in this target method or is in the adapter frame stub
     *         code, prologue or epilogue.
     */
    public int bciFor(CodePointer ip) {
        assert bciToPos != null;
        assert bciToPos.length > 0;
        final int pos = posFor(ip);
        return bciForPos(pos);
    }


    /**
     * Gets the BCI for a target code position in this target method.
     *
     * @param pos a target code position that may denote an instruction in this method correlated with a bytecode
     * @return the start position of the bytecode instruction that is implemented at {@code pos} or -1 if
     *         {@code pos} is outside the range(s) of target code positions in this target method that
     *         correlate with a bytecode.
     */
    public int bciForPos(int pos) {
        assert bciToPos != null;
        assert bciToPos.length > 0;
        int bci;
        if (pos >= posForBci(0)) {
            bci = -1;
            // Search the map backwards as there may be bytecodes for which
            // no target code was emitted. The search is for the first bytecode
            // position that maps to a non-zero target code position less than or
            // equal to 'pos'
            for (int i = bciToPos.length - 1; i >= 0; --i) {
                int p = posForBci(i);
                if (p != 0) {
                    if (p <= pos) {
                        // This is the first bytecode that maps to a non-zero target
                        // code position less than or equal to 'pos'
                        bci = i;
                        break;
                    }
                }
            }
            assert bci >= 0;
            return bci;
        }
        // The instruction pointer denotes a position in the adapter frame code or the prologue
        return -1;
    }

    /**
     * Ensures that the {@linkplain #referenceMaps() reference maps} for this method are finalized. Only
     * finalized reference maps are guaranteed to never change for the remaining lifetime of this target method.
     * <p/>
     * Although this method may be called by multiple threads, it cannot use standard synchronization as that may block
     * one of the threads in native code on a mutex. This would incorrectly be interpreted by the GC as meaning
     * the mutator thread has blocked for GC after taking a safepoint trap. To avoid blocking in native code,
     * a spin loop is used instead.
     * <p/>
     * If this method is called while preparing the stack reference map for a thread that has taken a safepoint
     * for GC, then safepoints are currently disabled and so there is no need to use the {@link T1XTemplate#NO_SAFEPOINTS}
     * annotation on this method.
     */
    @Override
    public void finalizeReferenceMaps() {
        Object object = this.refMapEditor.get();
        if (object != null) {
            T1XReferenceMapEditor referenceMapEditor = null;
            Object result = object;
            if (object instanceof T1XReferenceMapEditor) {
                referenceMapEditor = (T1XReferenceMapEditor) object;
                result = this.refMapEditor.compareAndSwap(referenceMapEditor, VmThread.current());
            }
            if (result == referenceMapEditor) {
                // We must disable safepoint polls in the current thread to prevent any recursive
                // attempt to finalize the ref maps. Such a recursive call will spin infinitely
                // in the pause loop below.
                // One case where such recursion is possible is if a GC is requested while
                // this thread is preparing ref maps during ref map verification (i.e. -XX:+VerifyRefMaps).
                boolean mustReenableSafepoints = !SafepointPoll.disable();

                referenceMapEditor.fillInMaps();
                this.refMapEditor.set(null);

                if (mustReenableSafepoints) {
                    SafepointPoll.enable();
                }
            } else if (result != null) {
                FatalError.check(result instanceof VmThread, "expected VmThread instance");
                if (VmThread.current() == result) {
                    Log.print("Recursive attempt to finalize ref maps of ");
                    Log.printMethod(this, true);
                    throw FatalError.unexpected("Recursive attempt to finalize ref maps of a T1X target method", false, null, Pointer.zero());
                }
                // Spin while waiting for the other thread to complete finalization
                while (refMapEditor.get() != null) {
                    Intrinsics.pause();
                }
            }
        }
    }

    @Override
    public boolean preserveRegistersForLocalExceptionHandler() {
        return false;
    }

    @Override
    public CodePointer throwAddressToCatchAddress(CodePointer throwAddress, Throwable exception) {
        return throwAddressToCatchAddress(throwAddress, exception, null);
    }

    @Override
    public boolean catchExceptionInfo(StackFrameCursor current, Throwable throwable, CatchExceptionInfo info) {
        CodePointer codePointer = throwAddressToCatchAddress(current.vmIP(), throwable, info);
        if (!codePointer.isZero()) {
            info.codePointer = codePointer;
            return true;
        } else {
            return false;
        }
    }

    private CodePointer throwAddressToCatchAddress(CodePointer ip, Throwable exception, CatchExceptionInfo info) {
        if (handlers.length != 0) {
            final int exceptionPos = posFor(ip);
            int exceptionBCI = bciForPos(exceptionPos);
            if (exceptionBCI != -1) {
                for (CiExceptionHandler e : handlers) {
                    if (e.catchTypeCPI != SYNC_METHOD_CATCH_TYPE_CPI) {
                        if (e.covers(exceptionBCI)) {
                            ClassActor catchType = (ClassActor) e.catchType;
                            if (catchType == null || catchType.isAssignableFrom(ObjectAccess.readClassActor(exception))) {
                                int handlerPos = posForBci(e.handlerBCI());
                                checkHandler(exceptionPos, exceptionBCI, e.handlerBCI, handlerPos);
                                if (info != null) {
                                    info.bci = e.handlerBCI();
                                }
                                return codeAt(handlerPos);
                            }
                        }
                    }
                }
            }
            if (handlers[handlers.length - 1].catchTypeCPI == SYNC_METHOD_CATCH_TYPE_CPI) {
                CiExceptionHandler syncMethodHandler = handlers[handlers.length - 1];
                if (syncMethodHandler.covers(exceptionPos)) {
                    int handlerPos = syncMethodHandler.handlerBCI;
                    checkHandler(exceptionPos, exceptionBCI, -1, handlerPos);
                    return codeAt(handlerPos);
                }
            }
        }
        return CodePointer.zero();
    }

    void checkHandler(int excPos, int excBCI, int handlerBCI, int handlerPos) {
        if (handlerPos <= 0 || handlerPos >= code().length) {
            FatalError.unexpected("Bad handler for exception at pos " + excPos + " (bci: " + excBCI + ") in " + this + ": handler pos " + handlerPos + " (bci: " + handlerBCI + ")");
        }
    }

    private int bciForCallSite(CodePointer returnIP) {
        // The instruction pointer is now just beyond the call machine instruction.
        // In case the call happens to be the last machine instruction for the invoke bytecode we are interested in, we subtract one byte.
        // Thus we always look up what bytecode we were in during the call.
        return bciFor(returnIP.minus(1));
    }

    private static int getInvokeCPI(byte[] code, int invokeBCI) {
        assert invokeBCI >= 0 : "illegal bytecode index";
        assert invokeBCI + 2 < code.length : "illegal bytecode index";
        assert code[invokeBCI] == (byte) Bytecodes.INVOKEINTERFACE ||
            code[invokeBCI] == (byte) Bytecodes.INVOKESPECIAL ||
            code[invokeBCI] == (byte) Bytecodes.INVOKESTATIC ||
            code[invokeBCI] == (byte) Bytecodes.INVOKEVIRTUAL ||
            code[invokeBCI] == (byte) Bytecodes.INVOKEDYNAMIC
            : "expected invoke bytecode";
        return ((code[invokeBCI + 1] & 0xff) << 8) | (code[invokeBCI + 2] & 0xff);
    }

    /**
     * Prepares the reference map to cover the reference parameters on the stack at a call from a T1X compiled method
     * into a trampoline. These slots are normally ignored when computing the reference maps for a T1X method as they
     * are covered by a reference map in the callee if necessary. They <b>cannot</b> be covered by a reference map in
     * the T1X method as these slots are seen as local variables in a T1X callee and as such can be overwritten with
     * non-reference values.
     * <p/>
     * However, in the case where a T1X method calls into a trampoline, the reference parameters of the call are not
     * covered by any reference map. In this situation, we need to analyze the invokeXXX bytecode at the call site to
     * derive the signature of the call which in turn allows us to mark the parameter stack slots that contain
     * references.
     *
     * @param caller the T1X method frame cursor
     */
    private void prepareTrampolineRefMap(StackFrameCursor caller, FrameReferenceMapVisitor preparer) {
        // prepare the reference map for the parameters passed by the current (caller) frame.
        // the call was unresolved and hit a trampoline, so compute the refmap from the signature of
        // the called method by looking at the bytecode of the caller method
        CodePointer cip = caller.vmIP();
        if (Code.getCodeManager().getRuntimeBaselineCodeRegion().isInFromSpace(cip.toAddress())) {
            cip = relocateIP(cip);
        }
        int bci = bciForCallSite(cip);
        ConstantPool constantPool = codeAttribute.cp;
        byte[] code = codeAttribute.code();
        MethodRefConstant methodRef = constantPool.methodAt(getInvokeCPI(code, bci));
        boolean isInvokestatic = (code[bci] & 0xFF) == Bytecodes.INVOKESTATIC;
        SignatureDescriptor sig = methodRef.signature(constantPool);

        int numberOfSlots = sig.computeNumberOfSlots() + (isInvokestatic ? 0 : 1);

        if (numberOfSlots != 0) {
            // Handle the parameters in reverse order as caller.sp() is currently
            // pointing at the last parameter.
            Pointer slotPointer = caller.sp();
            for (int i = sig.numberOfParameters() - 1; i >= 0; --i) {
                TypeDescriptor parameter = sig.parameterDescriptorAt(i);
                Kind parameterKind = parameter.toKind();
                if (parameterKind.isReference) {
                    if (logStackRootScanning()) {
                        StackReferenceMapPreparer.stackRootScanLogger.logParameter(i, parameter);
                    }
                    preparer.visitReferenceMapBits(caller, slotPointer, 1, 1);
                }
                int parameterSlots = (!parameterKind.isCategory1) ? 2 : 1;
                slotPointer = slotPointer.plus(JVMSFrameLayout.JVMS_SLOT_SIZE * parameterSlots);
            }

            // Finally deal with the receiver (if any)
            if (!isInvokestatic) {
                // Mark the slot for the receiver as it is not covered by the method signature:
                if (logStackRootScanning()) {
                    StackReferenceMapPreparer.stackRootScanLogger.logReceiver(methodRef.holder(constantPool));
                }
                preparer.visitReferenceMapBits(caller, slotPointer, 1, 1);
            }
        }
    }

    /**
     * @return the number of bytes in {@link #refMaps} corresponding to one stop position.
     */
    int refMapSize() {
        return regRefMapSize() + frameRefMapSize;
    }

    @Override
    public void prepareReferenceMap(StackFrameCursor current, StackFrameCursor callee, FrameReferenceMapVisitor preparer) {
        finalizeReferenceMaps();

        CiCalleeSaveLayout csl = callee.csl();
        Pointer csa = callee.csa();
        TargetMethod calleeTM = callee.targetMethod();
        if (calleeTM != null) {
            Stub.Type st = calleeTM.stubType();
            if (st == StaticTrampoline || st == VirtualTrampoline || st == InterfaceTrampoline) {
                prepareTrampolineRefMap(current, preparer);
            } else if (calleeTM.is(TrapStub) && Trap.Number.isStackOverflow(csa)) {
                // a method can never catch stack overflow for itself so there
                // is no need to scan the references in the trapped method
                return;
            }
        }

        CodePointer cip = current.vmIP();
        if (Code.getCodeManager().getRuntimeBaselineCodeRegion().isInFromSpace(cip.toAddress())) {
            cip = relocateIP(cip);
        }
        int safepointIndex = findSafepointIndex(cip);
        if (safepointIndex < 0) {
            // this is very bad.
            throw FatalError.unexpected("could not find safepoint index");
        }
        int refMapSize = refMapSize();

        if (!csa.isZero()) {
            assert csl != null;
            // the callee contains register state from this frame;
            // use register reference maps in this method to fill in the map for the callee
            int byteIndex = (safepointIndex * refMapSize) + frameRefMapSize;
            preparer.logPrepareReferenceMap(this, safepointIndex, csa, "registers");
            // Need to translate from register numbers (as stored in the reg ref maps) to frame slots.
            for (int i = 0; i < regRefMapSize(); i++) {
                int b = refMaps[byteIndex] & 0xff;
                int reg = i * 8;
                while (b != 0) {
                    if ((b & 1) != 0) {
                        int offset = csl.offsetOf(reg);
                        if (logStackRootScanning()) {
                            StackReferenceMapPreparer.stackRootScanLogger.logRegisterState(csl.registers[reg]);
                        }
                        preparer.visitReferenceMapBits(callee, csa.plus(offset), 1, 1);
                    }
                    reg++;
                    b = b >>> 1;
                }
                byteIndex++;
            }
        }

        // prepare the map for this stack frame
        Pointer slotPointer = current.fp().plus(frameRefMapOffset);
        preparer.logPrepareReferenceMap(this, safepointIndex, slotPointer, "frame");
        int byteIndex = safepointIndex * refMapSize;
        for (int i = 0; i < frameRefMapSize; i++) {
            preparer.visitReferenceMapBits(current, slotPointer, refMaps[byteIndex] & 0xff, 8);
            slotPointer = slotPointer.plusWords(8);
            byteIndex++;
        }
    }

    private Pointer adjustSPForHandler(Pointer fp) {
        if (isAMD64() || isARM() || isAARCH64() || isRISCV64()) {
            // The Java operand stack of the T1X method that handles the exception is cleared
            // when unwinding. The T1X generated handler is responsible for loading the
            // exception from VmThreadLocal.EXCEPTION_OBJECT to the operand stack.
            //
            // Compute the offset to the first stack slot of the Java Stack:
            //
            //    frame size - (space for locals + saved RBP + space of the first slot itself).
            //
            Pointer catcherSP = fp.minus(sizeOfNonParameterLocals());

            // The ref maps for the handler address will expect a valid reference to
            // to in stack slot 0 so we store a null there.
            Pointer slot0 = catcherSP.minus(JVMS_SLOT_SIZE);
            for (int i = 0; i < STACK_SLOTS_PER_JVMS_SLOT; i++) {
                slot0.writeReference(i, Reference.zero());
            }

            return catcherSP;
        } else {
            throw unimplISA();
        }
    }

    static CodePointer throwAddress(StackFrameCursor frame) {
        if (!frame.isTopFrame() && platform().isa.offsetToReturnPC == 0) {
            // Adjust 'retAddr' to ensure it is within the call instruction.
            return frame.vmIP().minus(1);
        } else {
            return frame.vmIP();
        }
    }

    private int bytecodeSize() {
        return bciToPos.length - 1;
    }

    private void recordExceptionSeen(StackFrameCursor current) {
        if (profile != null) {
            int bci = bciFor(current.vmIP());
            if (bci < bytecodeSize()) {
                assert bci >= 0;
                int mpoIndex = profile.getExceptionSeenProfileDataIndex(bci);
                if (mpoIndex == MethodProfile.UNDEFINED_INDEX) {
                    // Exception for this bci is not utilized in Graal so it is not recorded
                    if (PRINT_WARNING_ON_NOT_PROFILED_EXCEPTION) {
                        TTY.println("WARNING: Profile index not found to record exception for %s at bci: %d", classMethodActor, bci);
                    }
                } else {
                    MethodInstrumentation.recordExceptionSeen(profile, mpoIndex);
                }
            } else {
                // Do not record exception rethrown in T1X epilogue
                assert bci == bytecodeSize();
            }
        }
    }

    @Override
    public void catchException(StackFrameCursor current, StackFrameCursor callee, StackUnwindingContext context) {
        StackFrameWalker sfw = current.stackFrameWalker();
        Throwable throwable = context.throwable;
        CodePointer throwAddress = throwAddress(current);
        CodePointer catchAddress = throwAddressToCatchAddress(throwAddress, throwable);

        recordExceptionSeen(current);
        if (!catchAddress.isZero()) {
            if (StackFrameWalker.TraceStackWalk) {
                Log.print("StackFrameWalk: Handler position for exception at position ");
                Log.print(current.vmIP().minus(codeStart()).toInt());
                Log.print(" is ");
                Log.println(catchAddress.minus(codeStart()).toInt());
            }

            if (isAMD64() || isARM() || isAARCH64() || isRISCV64()) {
                Pointer localVariablesBase = current.fp();
                Pointer catcherSP = adjustSPForHandler(current.fp());

                // Done with the stack walker
                sfw.reset();

                // Store the exception for the handler
                VmThread.current().storeExceptionForHandler(throwable, this, posFor(catchAddress));

                Stubs.unwind(catchAddress.toPointer(), catcherSP, localVariablesBase);
                FatalError.unexpected("should not reach here");
            } else {
                unimplISA();
            }
        }
    }

    @PLATFORM(cpu = "armv7")
    @HOSTED_ONLY
    private FramePointerStateARMV7 computeFramePointerStateARMV7(StackFrameCursor current, StackFrameWalker stackFrameWalker, CodePointer lastPrologueInstr) {
        // Checkstyle: stop
        final byte ENTER = (byte) 0xC8;
        final byte LEAVE = (byte) 0xC9;
        final byte POP_RBP = (byte) 0x5D;

        final byte RET = (byte) 0xC3;
        final byte RET2 = (byte) 0xC2;
        // Checkstyle: resume


        CodePointer ip = current.vmIP();
        byte byteAtIP = stackFrameWalker.readByte(ip.toPointer(), 0);
        if (ip.toPointer().lessThan(lastPrologueInstr.toPointer()) || byteAtIP == ENTER || byteAtIP == RET || byteAtIP == RET2) {
            return FramePointerStateARMV7.CALLER_FRAME_IN_RBP;
        }
        if (ip.equals(lastPrologueInstr) || byteAtIP == LEAVE) {
            return FramePointerStateARMV7.CALLER_FRAME_AT_RBP;
        }
        if (byteAtIP == POP_RBP) {
            return FramePointerStateARMV7.RETURNING_FROM_RUNTIME;
        }
        return FramePointerStateARMV7.IN_RBP;
    }

    @PLATFORM(cpu = "amd64")
    @HOSTED_ONLY
    private FramePointerStateAMD64 computeFramePointerState(StackFrameCursor current, StackFrameWalker stackFrameWalker, CodePointer lastPrologueInstr) {
        // Checkstyle: stop
        final byte ENTER = (byte) 0xC8;
        final byte LEAVE = (byte) 0xC9;
        final byte POP_RBP = (byte) 0x5D;

        final byte RET = (byte) 0xC3;
        final byte RET2 = (byte) 0xC2;
        // Checkstyle: resume


        CodePointer ip = current.vmIP();
        byte byteAtIP = stackFrameWalker.readByte(ip.toPointer(), 0);
        if (ip.toPointer().lessThan(lastPrologueInstr.toPointer()) || byteAtIP == ENTER || byteAtIP == RET || byteAtIP == RET2) {
            return FramePointerStateAMD64.CALLER_FRAME_IN_RBP;
        }
        if (ip.equals(lastPrologueInstr) || byteAtIP == LEAVE) {
            return FramePointerStateAMD64.CALLER_FRAME_AT_RBP;
        }
        if (byteAtIP == POP_RBP) {
            return FramePointerStateAMD64.RETURNING_FROM_RUNTIME;
        }
        return FramePointerStateAMD64.IN_RBP;
    }

    @Override
    @HOSTED_ONLY
    public boolean acceptStackFrameVisitor(StackFrameCursor current, StackFrameVisitor visitor) {
        StackFrameWalker sfw = current.stackFrameWalker();
        Pointer localVariablesBase;
        CodePointer startOfPrologue;
        AdapterGenerator generator = AdapterGenerator.forCallee(this);
        StackFrame stackFrame;
        if (generator != null) {
            startOfPrologue = codeAt(generator.prologueSizeForCallee(classMethodActor));
        } else {
            startOfPrologue = codeStart();
        }
        if (isAMD64()) {
            CodePointer lastPrologueInstruction = startOfPrologue.plus(FramePointerStateAMD64.OFFSET_TO_LAST_PROLOGUE_INSTRUCTION);
            FramePointerStateAMD64 framePointerState = computeFramePointerState(current, sfw, lastPrologueInstruction);
            localVariablesBase = framePointerState.localVariablesBase(current);
            stackFrame = new AMD64JVMSFrame(sfw.calleeStackFrame(), current.targetMethod(), current.vmIP().toPointer(), current.sp(), localVariablesBase, localVariablesBase);
        } else if (isARM()) {
            CodePointer lastPrologueInstruction = startOfPrologue.plus(FramePointerStateARMV7.OFFSET_TO_LAST_PROLOGUE_INSTRUCTION);
            FramePointerStateARMV7 framePointerState = computeFramePointerStateARMV7(current, sfw, lastPrologueInstruction);
            localVariablesBase = framePointerState.localVariablesBase(current);
            stackFrame = new ARMV7JVMSFrame(sfw.calleeStackFrame(), current.targetMethod(), current.vmIP().toPointer(), current.sp(), localVariablesBase, localVariablesBase);
        } else {
            throw unimplISA();
        }
        return visitor.visitFrame(stackFrame);
    }

    @Override
    public void advance(StackFrameCursor current) {
        StackFrameWalker sfw = current.stackFrameWalker();
        int dispToRip = frameSize() - sizeOfNonParameterLocals();
        Pointer returnRIP = current.fp().plus(dispToRip);
        int slotSize = Word.size();
        if (isAARCH64() || isRISCV64()) { // On Aarch64 stack is 16-byte aligned, so we use this as the slot size
            slotSize = target().stackAlignment;
        }
        Pointer callerFP = sfw.readWord(returnRIP, -slotSize).asPointer();
        if (MaxineVM.isHosted()) {
            // Inspector context only
            CodePointer startOfPrologue;
            AdapterGenerator generator = AdapterGenerator.forCallee(this);
            if (generator != null) {
                if (generator.advanceIfInPrologue(current)) {
                    return;
                }
                startOfPrologue = codeAt(generator.prologueSizeForCallee(classMethodActor));
            } else {
                startOfPrologue = codeStart();
            }

            if (isAMD64()) {
                CodePointer lastPrologueInstruction = startOfPrologue.plus(FramePointerStateAMD64.OFFSET_TO_LAST_PROLOGUE_INSTRUCTION);
                FramePointerStateAMD64 framePointerState = computeFramePointerState(current, sfw, lastPrologueInstruction);
                returnRIP = framePointerState.returnIP(current);
                callerFP = framePointerState.callerFP(current);
            } else if (isARM()) {
                CodePointer lastPrologueInstruction = startOfPrologue.plus(FramePointerStateARMV7.OFFSET_TO_LAST_PROLOGUE_INSTRUCTION);
                FramePointerStateARMV7 framePointerState = computeFramePointerStateARMV7(current, sfw, lastPrologueInstruction);
                returnRIP = framePointerState.returnIP(current);
                callerFP = framePointerState.callerFP(current);
            } else {
                unimplISA();
            }

        }
        Pointer callerIP = sfw.readWord(returnRIP, 0).asPointer();
        Pointer callerSP = returnRIP.plus(slotSize); // Skip the rip

        int stackAmountInBytes = classMethodActor.numberOfParameterSlots() * JVMSFrameLayout.JVMS_SLOT_SIZE;
        if (stackAmountInBytes != 0) {
            callerSP = callerSP.plus(stackAmountInBytes);
        }

        boolean wasDisabled = SafepointPoll.disable();
        sfw.advance(callerIP, callerSP, callerFP);
        if (!wasDisabled) {
            SafepointPoll.enable();
        }
    }

    @Override
    public Pointer returnAddressPointer(StackFrameCursor frame) {
        if (isAMD64() || isARM() || isAARCH64() || isRISCV64()) {
            int dispToRip = frameSize() - sizeOfNonParameterLocals();
            return frame.fp().plus(dispToRip);
        } else {
            throw unimplISA();
        }
    }

    @Override
    public boolean isBaseline() {
        return true;
    }

    @Override
    public Continuation createDeoptimizedFrame(Info info, CiFrame frame, Continuation cont, Throwable exception, boolean reexecute) {
        int bci = frame.bci;
        ClassMethodActor method = classMethodActor;
        assert classMethodActor == frame.method : classMethodActor + " != " + frame.method;
        CodePointer ip = findContinuationIP(info, exception, bci, reexecute);

        // record continuation instruction pointer
        cont.setIP(info, ip.toPointer());

        if (exception == null) {
            // record continuation stack pointer
            cont.setSP(info, CiConstant.forJsr(info.slotsCount()));
            // add operand stack slots
            for (int i = frame.numStack - 1; i >= 0; i--) {
                CiConstant value = (CiConstant) frame.getStackValue(i);
                info.addSlot(value, "ostack");
                addSlotPadding(info, "ostack (pad)");
            }
        } else {
            assert frame.numStack == 0 ||
                frame.rethrowException && frame.numStack == 1
                : "operand stack must be clear at exception handler or contain a single slot with exception";
            // The ref maps for the handler address will expect a valid reference to exception in stack slot 0
            assert STACK_SLOTS_PER_JVMS_SLOT == 2;
            for (int pad = 0; pad < 1; pad++) {
                CiConstant value = CiConstant.forObject(exception);
                info.addSlot(value, "ostack");
                info.addSlot(WordUtil.ZERO, "ostack (pad)");
            }

            // The continuation stack pointer must denote an empty stack
            cont.setSP(info, CiConstant.forJsr(info.slotsCount()));
        }

        int paramLocals = method.numberOfParameterSlots();
        int numLocals = frame.numLocals;
        int synchronizedReceiver = -1;
        if (method.isSynchronized() && !method.isStatic()) {
            assert frame.numLocks > 0;
            synchronizedReceiver = numLocals;
            numLocals++;
        }
        int nonParamLocals = numLocals - paramLocals;

        // add (non-parameter) local slots
        for (int i = numLocals - 1; i >= paramLocals; i--) {
            if (i == synchronizedReceiver) {
                CiConstant value = (CiConstant) frame.getLockValue(0);
                info.addSlot(value, "locked rcvr");
            } else {
                CiConstant value = (CiConstant) frame.getLocalValue(i);
                info.addSlot(value, "local");
            }
            addSlotPadding(info, "local (pad)");
        }

        // record continuation frame pointer
        cont.setFP(info, CiConstant.forJsr(info.slotsCount()));

        // add template slots
        for (int i = 0; i < templateSlots; i++) {
            info.addSlot(WordUtil.ZERO, "template");
        }

        // add alignment slots
        int fpSlots = target().arch.isAarch64() || target().arch.isRISCV64() ? 2 : 1; // one (or two) extra word(s) for the caller FP
        int numberOfSlots = fpSlots + templateSlots;
        int unalignedSize = (numberOfSlots + (nonParamLocals * STACK_SLOTS_PER_JVMS_SLOT)) * STACK_SLOT_SIZE;
        int alignedSize = target().alignFrameSize(unalignedSize);
        int alignmentSlots = (alignedSize - unalignedSize) / STACK_SLOT_SIZE;
        for (int i = 0; i < alignmentSlots; i++) {
            info.addSlot(WordUtil.ZERO, "align");
        }

        // add caller FP slot with placeholder value
        int callerFPIndex = info.slotsCount();
        info.addSlot(WordUtil.ZERO, "callerFP");
        // in Aarch64 FP and LR are pushed separately and each one takes a whole JVMS_SLOT
        if (target().arch.isAarch64() || target().arch.isRISCV64()) {
            addSlotPadding(info, "callerFP (pad)");
        }

        // add caller return address slot with placeholder value
        int returnAddressIndex = info.slotsCount();
        info.addSlot(WordUtil.ZERO, "returnIP");
        // in Aarch64 FP and LR are pushed separately and each one takes a whole JVMS_SLOT
        if (target().arch.isAarch64() || target().arch.isRISCV64()) {
            addSlotPadding(info, "returnIP (pad)");
        }

        // add parameter slots
        for (int i = paramLocals - 1; i >= 0; i--) {
            CiConstant value = (CiConstant) frame.getLocalValue(i);
            info.addSlot(value, "param");
            addSlotPadding(info, "param (pad)");
        }

        return new CallerContinuation(callerFPIndex, -1, returnAddressIndex);
    }

    /**
     * Adds any per-JVMS slot padding required. The padding occupies the higher slots as per {@link JVMSFrameLayout#JVMS_SLOT_SIZE}.
     */
    private void addSlotPadding(Info info, String desc) {
        for (int pad = 1; pad < STACK_SLOTS_PER_JVMS_SLOT; pad++) {
            info.addSlot(WordUtil.ZERO, desc);
        }
    }

    /**
     * Finds the address at which deoptimized execution will continue in this target method
     * based on a BCI specifying the bytecode instruction that should be resumed.
     *
     * @param info      details of current deoptimization
     * @param exception if non-null, then the returned address will be for the handler of this exception
     * @param bci       the BCI specified by a debug info {@linkplain CiFrame frame}
     * @param reexecute specifies if the instruction at {@code bci} is to be re-executed
     */
    private CodePointer findContinuationIP(Info info, Throwable exception, int bci, boolean reexecute) throws FatalError {
        CodePointer ip;
        assert info.tm != null;
        assert bci != -1 : "Unable to find deoptimized execution continuation for " + info.tm + "+" + info.ip.pos();
        if (exception == null) {
            RiMethod callee = classMethodActor.codeAttribute().calleeAt(bci);
            if (reexecute) {
                int curPos = bciToPos[bci];
                ip = codeAt(curPos);
            } else {
                ip = findTemplateCallReturnAddress(info, bci, callee);
            }
        } else {
            // Unwinding to deoptimized frame containing the handler for 'exception'
            int curPos = bciToPos[bci];
            CodePointer handler = throwAddressToCatchAddress(codeAt(curPos), exception);
            assert !handler.isZero() : "could not (re)find handler for " + exception + " thrown at " + this + "+" + curPos;
            ip = handler;
        }
        return ip;
    }

    /**
     * Finds the address of the instruction after a template call.
     *
     * @param info   details of current deoptimization
     * @param bci    BCI of the bytecode invoke instruction that was translated to a template call
     * @param callee the callee method at bci in this target method, or null if none.
     */
    protected CodePointer findTemplateCallReturnAddress(Info info, int bci, RiMethod callee) throws FatalError {
        int templateCallReturnPos = -1;
        int curPos = bciToPos[bci];
        if (callee != null) {
            // Must be in a call
            // Find the instruction *following* the template call by searching backwards
            // from the template emitted for the instruction after the invoke
            byte[] bytecode = classMethodActor.code();
            assert bytecode != null;
            int opcode = bytecode[bci] & 0xFF;
            assert opcode == INVOKEINTERFACE || opcode == INVOKESPECIAL || opcode == INVOKESTATIC || opcode == INVOKEVIRTUAL;
            final int invokeSize = Bytecodes.lengthOf(opcode);
            int succBCI = bci + invokeSize;
            int succPos = bciToPos[succBCI];
            assert succPos > curPos;

            for (int safepointIndex = 0; safepointIndex < safepoints.size(); ++safepointIndex) {
                int safepointPos = safepoints.posAt(safepointIndex);
                if (curPos <= safepointPos && safepointPos < succPos) {
                    if (safepoints.isSetAt(TEMPLATE_CALL, safepointIndex)) {
                        if (isAMD64() || isAARCH64() || isRISCV64()) {
                            // On x86 the safepoint position of a call *is* the return position
                            templateCallReturnPos = safepointPos;
                        } else if (isARM()) {
                            /* On ARMv7 we do a call like this:
                                movw r12, constLOW
                                movt r12, constHIGH
                                add  r12, r12, PC
                                blx r12
                                RETURN POINT <------ return address
                             */
                            templateCallReturnPos = safepointPos + 16;
                        } else {
                            throw unimplISA();
                        }
                        break;
                    }
                }
            }
        }
        if (templateCallReturnPos != -1) {
            return codeAt(templateCallReturnPos);
        } else {
            FatalError.check(callee == null || ((RiResolvedMethod) callee).intrinsic() != null, "could not find template call for non-intrinsic method at " + curPos + " in " + this);
            // Must be a safepoint
            return codeAt(curPos);
        }
    }
}

/**
 * Various execution states in a T1X method that can only be observed in
 * the context of the Inspector.
 */
@HOSTED_ONLY
@PLATFORM(cpu = "armv7")
enum FramePointerStateARMV7 {
    /**
     * RBP holds the frame pointer of the current method activation. caller's RIP is at [RBP + FrameSize], caller's
     * frame pointer is at [RBP + FrameSize -1]
     */
    IN_RBP {
        @Override
        Pointer localVariablesBase(StackFrameCursor current) {
            return current.fp();
        }

        @Override
        Pointer returnIP(StackFrameCursor current) {
            T1XTargetMethod targetMethod = (T1XTargetMethod) current.targetMethod();
            int dispToRip = targetMethod.frameSize() - targetMethod.sizeOfNonParameterLocals();
            return current.fp().plus(dispToRip);
        }

        @Override
        Pointer callerFP(StackFrameCursor current) {
            return current.stackFrameWalker().readWord(returnIP(current), -Word.size()).asPointer();
        }
    },

    /**
     * RBP holds the frame pointer of the caller, caller's RIP is at [RSP] This state occurs when entering the
     * method or exiting it.
     */
    CALLER_FRAME_IN_RBP {
        @Override
        Pointer localVariablesBase(StackFrameCursor current) {
            int offsetToSaveArea = current.targetMethod().frameSize();
            return current.sp().minus(offsetToSaveArea);
        }

        @Override
        Pointer returnIP(StackFrameCursor current) {
            return current.sp();
        }

        @Override
        Pointer callerFP(StackFrameCursor current) {
            return current.fp();
        }
    },

    /**
     * RBP points at the bottom of the "saving area". Caller's frame pointer is at [RBP], caller's RIP is at [RBP +
     * WordSize].
     */
    CALLER_FRAME_AT_RBP {
        @Override
        Pointer localVariablesBase(StackFrameCursor current) {
            T1XTargetMethod targetMethod = (T1XTargetMethod) current.targetMethod();
            int dispToFrameStart = targetMethod.frameSize() - (targetMethod.sizeOfNonParameterLocals() + Word.size());
            return current.fp().minus(dispToFrameStart);
        }

        @Override
        Pointer returnIP(StackFrameCursor current) {
            return current.fp().plus(Word.size());
        }

        @Override
        Pointer callerFP(StackFrameCursor current) {
            return current.stackFrameWalker().readWord(current.fp(), 0).asPointer();
        }
    },

    /**
     * Returning from a runtime call (or actually in a runtime call). RBP may have been clobbered by the runtime.
     * The frame pointer for the current activation record is 'RSP + stack slot size'.
     */
    RETURNING_FROM_RUNTIME {
        @Override
        Pointer localVariablesBase(StackFrameCursor current) {
            return current.stackFrameWalker().readWord(current.sp(), 0).asPointer();
        }

        @Override
        Pointer returnIP(StackFrameCursor current) {
            T1XTargetMethod targetMethod = (T1XTargetMethod) current.targetMethod();
            int dispToRip = targetMethod.frameSize() - targetMethod.sizeOfNonParameterLocals();
            return localVariablesBase(current).plus(dispToRip);
        }

        @Override
        Pointer callerFP(StackFrameCursor current) {
            return current.stackFrameWalker().readWord(returnIP(current), -Word.size()).asPointer();
        }
    };

    abstract Pointer localVariablesBase(StackFrameCursor current);

    abstract Pointer returnIP(StackFrameCursor current);

    abstract Pointer callerFP(StackFrameCursor current);

    /**
     * Offset to the last instruction of the prologue from the JIT entry point. The prologue comprises two instructions,
     * the first one of which is enter (fixed size, 4 bytes long).
     */
    public static final int OFFSET_TO_LAST_PROLOGUE_INSTRUCTION = 4;


}

@HOSTED_ONLY
@PLATFORM(cpu = "amd64")
enum FramePointerStateAMD64 {
    /**
     * RBP holds the frame pointer of the current method activation. caller's RIP is at [RBP + FrameSize], caller's
     * frame pointer is at [RBP + FrameSize -1]
     */
    IN_RBP {
        @Override
        Pointer localVariablesBase(StackFrameCursor current) {
            return current.fp();
        }

        @Override
        Pointer returnIP(StackFrameCursor current) {
            T1XTargetMethod targetMethod = (T1XTargetMethod) current.targetMethod();
            int dispToRip = targetMethod.frameSize() - targetMethod.sizeOfNonParameterLocals();
            return current.fp().plus(dispToRip);
        }

        @Override
        Pointer callerFP(StackFrameCursor current) {
            return current.stackFrameWalker().readWord(returnIP(current), -Word.size()).asPointer();
        }
    },

    /**
     * RBP holds the frame pointer of the caller, caller's RIP is at [RSP] This state occurs when entering the
     * method or exiting it.
     */
    CALLER_FRAME_IN_RBP {
        @Override
        Pointer localVariablesBase(StackFrameCursor current) {
            int offsetToSaveArea = current.targetMethod().frameSize();
            return current.sp().minus(offsetToSaveArea);
        }

        @Override
        Pointer returnIP(StackFrameCursor current) {
            return current.sp();
        }

        @Override
        Pointer callerFP(StackFrameCursor current) {
            return current.fp();
        }
    },

    /**
     * RBP points at the bottom of the "saving area". Caller's frame pointer is at [RBP], caller's RIP is at [RBP +
     * WordSize].
     */
    CALLER_FRAME_AT_RBP {
        @Override
        Pointer localVariablesBase(StackFrameCursor current) {
            T1XTargetMethod targetMethod = (T1XTargetMethod) current.targetMethod();
            int dispToFrameStart = targetMethod.frameSize() - (targetMethod.sizeOfNonParameterLocals() + Word.size());
            return current.fp().minus(dispToFrameStart);
        }

        @Override
        Pointer returnIP(StackFrameCursor current) {
            return current.fp().plus(Word.size());
        }

        @Override
        Pointer callerFP(StackFrameCursor current) {
            return current.stackFrameWalker().readWord(current.fp(), 0).asPointer();
        }
    },

    /**
     * Returning from a runtime call (or actually in a runtime call). RBP may have been clobbered by the runtime.
     * The frame pointer for the current activation record is 'RSP + stack slot size'.
     */
    RETURNING_FROM_RUNTIME {
        @Override
        Pointer localVariablesBase(StackFrameCursor current) {
            return current.stackFrameWalker().readWord(current.sp(), 0).asPointer();
        }

        @Override
        Pointer returnIP(StackFrameCursor current) {
            T1XTargetMethod targetMethod = (T1XTargetMethod) current.targetMethod();
            int dispToRip = targetMethod.frameSize() - targetMethod.sizeOfNonParameterLocals();
            return localVariablesBase(current).plus(dispToRip);
        }

        @Override
        Pointer callerFP(StackFrameCursor current) {
            return current.stackFrameWalker().readWord(returnIP(current), -Word.size()).asPointer();
        }
    };

    abstract Pointer localVariablesBase(StackFrameCursor current);

    abstract Pointer returnIP(StackFrameCursor current);

    abstract Pointer callerFP(StackFrameCursor current);

    /**
     * Offset to the last instruction of the prologue from the JIT entry point. The prologue comprises two instructions,
     * the first one of which is enter (fixed size, 4 bytes long).
     */
    public static final int OFFSET_TO_LAST_PROLOGUE_INSTRUCTION = 4;
}
